package config

import (
	"context"
	"errors"
	"fmt"
	"net"
	"os"
	"os/signal"
	"ruoyi-go-echo-ddd/internal/infrastructure/persistence/prop"
	"ruoyi-go-echo-ddd/internal/infrastructure/utils"
	"sync"
	"syscall"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/rs/zerolog"
)

type NacosRedisConfig struct {
	Redis prop.RedisConfig `yaml:"redis"`
}

// GlobalStartTime 全局启动时间
var GlobalStartTime time.Time

// GlobalRedisClient 全局redis客户端
var GlobalRedisClient *redis.Client

var redisOnce = sync.OnceValues(func() (*redis.Client, error) {
	opts := &redis.Options{}
	redisConfig, err := GetConfigFromNacos[NacosRedisConfig](NacosClient, "redis", "DEFAULT_GROUP", "yml")
	if err != nil {
		zerolog.DefaultContextLogger.Error().Msgf("从nacos获取redis配置错误===%+v", err)
		zerolog.DefaultContextLogger.Info().Msgf("从默认配置获取redis配置===%s", utils.ToJsonFormat(RedisConfig))
		opts = &redis.Options{
			Addr:     RedisConfig.Address,
			Password: RedisConfig.Password,
			DB:       RedisConfig.Db,
			// 补充推荐配置项
			DialTimeout:  RedisConfig.DialTimeout,
			ReadTimeout:  RedisConfig.ReadTimeout,
			WriteTimeout: RedisConfig.WriteTimeout,
			PoolSize:     RedisConfig.PoolSize,
		}
	} else {
		zerolog.DefaultContextLogger.Info().Msgf("从nacos获取redis配置===%v", redisConfig.Redis)
		redisCfg := redisConfig.Redis
		if redisCfg.Address != "" && redisCfg.Password != "" {
			opts = &redis.Options{
				Addr:         redisCfg.Address,
				Password:     redisCfg.Password,
				DB:           redisCfg.Db,
				DialTimeout:  redisCfg.DialTimeout,
				ReadTimeout:  redisCfg.ReadTimeout,
				WriteTimeout: redisCfg.WriteTimeout,
				PoolSize:     redisCfg.PoolSize,
			}
		} else {
			opts = &redis.Options{
				Addr:         RedisConfig.Address,
				Password:     RedisConfig.Password,
				DB:           RedisConfig.Db,
				DialTimeout:  RedisConfig.DialTimeout,
				ReadTimeout:  RedisConfig.ReadTimeout,
				WriteTimeout: RedisConfig.WriteTimeout,
				PoolSize:     RedisConfig.PoolSize,
			}
		}
	}
	client := redis.NewClient(opts)
	if client == nil {
		zerolog.DefaultContextLogger.Fatal().Msg("redis客户端初始化失败")
		return nil, errors.New("redis客户端初始化失败")
	}

	// 测试连接是否有效（使用Ping）
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	if err = client.Ping(ctx).Err(); err != nil {
		var netErr net.Error
		if errors.As(err, &netErr) {
			zerolog.DefaultContextLogger.Error().Err(err).Msg("redis network error")
			return nil, fmt.Errorf("redis network error===%w", err)
		}
		zerolog.DefaultContextLogger.Error().Err(err).Msg("ping redis connection failed")
		return nil, err
	}

	ZeroLog.Info().Msgf("redis 客户端初始化成功, address===%s, db===%d", opts.Addr, opts.DB)
	return client, nil
})

func InitRedisClient() (*redis.Client, error) {
	client, err := redisOnce()
	if err != nil && client != nil {
		// 记录连接池状态（1分钟执行一次）
		go func() {
			ticker := time.NewTicker(time.Minute)
			defer ticker.Stop()

			for range ticker.C {
				stats := client.PoolStats()
				ZeroLog.Info().
					Int("hits", int(stats.Hits)).
					Int("misses", int(stats.Misses)).
					Int("timeouts", int(stats.Timeouts)).
					Int("total_conn", int(stats.TotalConns)).
					Msg("redis连接池状态")
			}
		}()
	}
	return client, err
}

// StartKeyExpirationListener 启动独立的过期事件监听（需在主程序调用）
//
//goland:noinspection ALL
func StartKeyExpirationListener(redisClient *redis.Client) (stopFunc func()) {
	ctx, cancel := context.WithCancel(context.Background())

	// 启动监听协程
	go func() {
		if redisClient == nil {
			ZeroLog.Error().Msg("Redis client not initialized")
			return
		}

		// 订阅过期事件
		pubSub := redisClient.PSubscribe(ctx, "__keyevent@*__:expired")
		defer pubSub.Close()

		// 信号处理
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

		// 消息处理循环
		for {
			select {
			case msg := <-pubSub.Channel():
				handleKeyExpiration(msg)
			case <-sigChan:
				ZeroLog.Info().Msg("Received shutdown signal")
				return
			case <-ctx.Done():
				ZeroLog.Info().Msg("Key listener stopped")
				return
			}
		}
	}()

	// 返回停止函数
	return func() {
		cancel()
		ZeroLog.Info().Msg("Stopping key expiration listener")
	}
}

// 示例处理函数（可自定义业务逻辑）
func handleKeyExpiration(msg *redis.Message) {
	ZeroLog.Info().Msgf("redis的key过期事件监听key==%s,channel==%s", msg.Payload, msg.Channel)
	// 在此添加业务逻辑
	// 例如：清理相关资源、发送通知等
}
